/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.cef.repository.adaptoritem;

import com.inspur.edp.cef.api.repository.DbParameter;
import com.inspur.edp.cef.repository.exception.CefRepositoryException;
import io.iec.caf.data.jpa.repository.CafEntityManagerWrapper;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.i18n.framework.api.InternalI18nContext;

import javax.persistence.EntityManager;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.*;

public class BatchSqlExecutor implements AutoCloseable {

  private LinkedHashMap<String, PreparedStatement> batchStmts;

  public void addBatch(String sql, List<DbParameter> pars) throws SQLException {
    Objects.requireNonNull(sql, "sql");

    if (batchStmts == null) {
      batchStmts = new LinkedHashMap<>(3);
    }
    PreparedStatement stmt = batchStmts.get(sql);
    if (stmt == null) {

      stmt = getStatement(sql);

      batchStmts.put(sql, stmt);
    }
    if (pars != null) {
      DbProcessor dbProc = getDBProc();
      for (int i = 0; i < pars.size(); ++i) {
        dbProc.setPar(stmt, i, pars.get(i));
      }
    }
    stmt.addBatch();
  }

  private CafEntityManagerWrapper wrapper;

  private PreparedStatement getStatement(String sql) throws SQLException {
    if (wrapper == null) {
      wrapper = new CafEntityManagerWrapper(SpringBeanUtils.getBean(EntityManager.class));
    }
    return wrapper.prepareStatement(sql);
  }

  private DbProcessor dbproc;
  private DbProcessor getDBProc() {
    if(dbproc == null) {
      dbproc = new DbProcessor();
    }
    return dbproc;
  }

  public void flushBatch() throws SQLException {
    if (batchStmts == null || batchStmts.isEmpty()) {
      return;
    }
    //resort
    for (PreparedStatement stmt : batchStmts.values()) {
      stmt.executeBatch();
      stmt.close();
    }
  }

  public void flushBatch(HashMap<String, HashMap> nodesInso) throws SQLException {
    if (batchStmts == null || batchStmts.isEmpty()) {
      return;
    }
    if(nodesInso == null || nodesInso.size() == 0){
      for (PreparedStatement stmt : batchStmts.values()) {
        stmt.executeBatch();
        stmt.close();
      }
    }
    else {
      List<PreparedStatement> sortStmt = new ArrayList<>();
      sortPre(nodesInso, sortStmt);
      //防止有未识别到的sql
      if(batchStmts !=null && batchStmts.size() > 0){
        sortStmt.addAll(batchStmts.values());
      }
      for(int i=0; i< sortStmt.size(); i++){
        sortStmt.get(i).executeBatch();
        sortStmt.get(i).close();
      }
    }
  }

  private void sortPre(HashMap<String, HashMap> map, List<PreparedStatement> sortMap){
    for(Map.Entry<String, HashMap> entry : map.entrySet()){
      String node = entry.getKey();
      String temp1 = "insert into " + node + " ";
      String temp2 = "update " + node + " ";
      String temp3 = "delete from " + node + " ";
      getSpecStmt(temp1, sortMap);
      getSpecStmt(temp2, sortMap);
      getSpecStmt(temp3, sortMap);
      sortPre(entry.getValue(), sortMap);
    }
  }

  private void getSpecStmt(String sqlTemp, List<PreparedStatement> list){
    if (batchStmts == null || batchStmts.isEmpty()) {
      return;
    }
    PreparedStatement preparedStatement = null;
    Iterator iterator = batchStmts.entrySet().iterator();
    while (iterator.hasNext()){
      Map.Entry<String, PreparedStatement> entry = (Map.Entry<String, PreparedStatement>) iterator.next();
      if(entry.getKey().toLowerCase().contains(sqlTemp.toLowerCase())){
        preparedStatement = entry.getValue();
        if(preparedStatement != null){
          list.add(preparedStatement);
        }
        iterator.remove();
      }
    }
  }

  @Override
  public void close() throws Exception {
    if (wrapper != null) {
      wrapper.close();
    }
  }

  public class DbProcessor {

    public void setPar(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      i++;
      switch (param.getDataType()) {
        case Jsonb:
          buildJsonbParmeter(stmt, i, param);
          break;
        case VarChar:
          buildVarcharParmeter(stmt, i, param);
          break;
        case NVarChar:
          buildNVarcharParmeter(stmt, i, param);
          break;
        case NChar:
          buildNCharParmeter(stmt, i, param);
          break;
        case Blob:
          buildBlobParameter(stmt, i, param);
          break;
        case Clob:
          //TODO 后续测试DM数据库是否合适
          buildClobParameter(stmt, i, param);
          break;
        case Int:
          buildIntParameter(stmt, i, param);
          break;
        case Decimal:
          buildDecimalParameter(stmt, i, param);
          break;
        case Char:
          buildCharParameter(stmt, i, param);
          break;
        case NClob:
          buildNClobParameter(stmt, i, param);
          break;
        case DateTime:
          buildDateTimeParameter(stmt, i, param);
          break;
        case Date:
          buildDateParameter(stmt, i, param);
          break;
        case Boolean:
          buildBooleanParameter(stmt,i,param);
          break;
        default:
          throw new CefRepositoryException("未知的数据类型，请确认参数的类型。");
      }
    }

    protected void buildJsonbParmeter(PreparedStatement stmt, int i, DbParameter param) throws SQLException {
      stmt.setObject(i,param.getValue(),Types.OTHER);
    }

    protected void buildVarcharParmeter(PreparedStatement stmt, int i, DbParameter param) throws SQLException {
      stmt.setObject(i, param.getValue());
    }

    protected void buildNVarcharParmeter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      stmt.setString(i, (String)param.getValue());
    }

    protected void buildNCharParmeter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      stmt.setString(i, (String)param.getValue());
    }

    protected void buildBlobParameter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      stmt.setBytes(i, (byte[]) param.getValue());
//      if(param.getValue() == null) {
//        stmt.setNull(i, Types.BLOB);
//      } else {
//        stmt.setBlob(i, new ByteArrayInputStream((byte[]) param.getValue()));
//      }
    }

    protected void buildClobParameter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      stmt.setString(i, (String)param.getValue());
//      if(param.getValue() == null) {
//        stmt.setNull(i, Types.CLOB);
//      } else {
//        stmt.setClob(i, new StringReader((String)param.getValue()));
//      }
    }

    protected void buildIntParameter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      if(param.getValue() == null) {
        stmt.setNull(i, Types.INTEGER);
      } else {
        stmt.setInt(i, Integer.valueOf(param.getValue().toString()));
      }
//      if (param.getValue() instanceof ArrayList) {
//        query.setParameter(i, param.getValue());
//      } else {
//        query
//            .setParameter(i, new TypedParameterValue(StandardBasicTypes.INTEGER, param.getValue()));
//      }
    }

    protected void buildDecimalParameter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      stmt.setBigDecimal(i, (BigDecimal) param.getValue());
//      if (param.getValue() instanceof ArrayList) {
//        query.setParameter(i, param.getValue());
//      } else {
//        query.setParameter(i,
//            new TypedParameterValue(StandardBasicTypes.BIG_DECIMAL, param.getValue()));
//      }
    }

    protected void buildCharParameter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      stmt.setString(i, (String)param.getValue());
//      if (param.getValue() == null) {
//        query.setParameter(i, new TypedParameterValue(StandardBasicTypes.STRING, param.getValue()));
//
//      } else {
//        query.setParameter(i, param.getValue());
//      }
    }

    protected void buildNClobParameter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      stmt.setString(i, (String)param.getValue());
      //query.setParameter(i, new TypedParameterValue(StandardBasicTypes.TEXT, param.getValue()));
    }

    protected void buildDateTimeParameter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      if(param.getValue() == null) {
        stmt.setNull(i, Types.TIMESTAMP);
      } else {
        stmt.setTimestamp(i, new Timestamp(((Date)param.getValue()).getTime()), getCalendar());
      }
      //query.setParameter(i, (Date) param.getValue(), TemporalType.DATE);
    }

    protected void buildBooleanParameter(PreparedStatement stmt, int i, DbParameter param)
            throws SQLException {
      if(param.getValue() == null) {
        stmt.setNull(i, Types.BOOLEAN);
      } else {
        stmt.setBoolean(i,(boolean)param.getValue());
      }
      //query.setParameter(i, (Date) param.getValue(), TemporalType.DATE);
    }

    protected void buildDateParameter(PreparedStatement stmt, int i, DbParameter param)
        throws SQLException {
      if(param.getValue() == null) {
        stmt.setNull(i, Types.DATE);
      } else {
        stmt.setDate(i, new java.sql.Date(((Date) param.getValue()).getTime()), getCalendar());
      }
//      if (param.getValue() == null) {
//        query.setParameter(i, (Date) param.getValue(), TemporalType.DATE);
//        return;
//      }
//
//      java.sql.Date date = new java.sql.Date(((Date) param.getValue()).getTime());
//      date = java.sql.Date.valueOf(date.toString());
//      query.setParameter(i, date, TemporalType.DATE);
    }

    private Calendar cal;
    protected Calendar getCalendar() {
      if(cal == null) {
        cal = Calendar.getInstance(SpringBeanUtils.getBean(InternalI18nContext.class).getDBTimezone());
      }
      return cal;
    }
  }
}
